#include "Session.hpp"
#include "BasicValue.pb.h"
#include "Request.pb.h"
#include "Response.pb.h"

namespace MRMDS::Server
{
using namespace boost::asio;

Message::BasicValue PackInt(int val)
{
    Message::BasicValue bv;
    bv.set_int32(val);
    return bv;
}
Message::BasicValue PackFloat(float val)
{
    Message::BasicValue bv;
    bv.set_float_(val);
    return bv;
}
Message::BasicValue PackDouble(double val)
{
    Message::BasicValue bv;
    bv.set_double_(val);
    return bv;
}
Message::BasicValue PackString(std::string val)
{
    Message::BasicValue bv;
    bv.set_string(val);
    return bv;
}

void Session::Start()
{
    socket.async_receive(buffer(buf, 4), std::bind(&Session::OnDataReceived, shared_from_this(), std::placeholders::_1,
                                                   std::placeholders::_2));
}

int Session::PackBuffer(Message::Frame &frame)
{
    auto timestamp = new google::protobuf::Timestamp();
    auto now = std::chrono::system_clock::now();
    double seconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
    double nanos = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count() - seconds * 1e9;
    timestamp->set_seconds(seconds);
    timestamp->set_nanos(nanos);
    frame.set_allocated_timestamp(timestamp);
    int size = frame.ByteSizeLong();
    if (frame.SerializePartialToArray(packBuf.begin() + 4, packBuf.size() - 4))
    {
        packBuf[3] = size | 0xFF;
        size >>= 8;
        packBuf[2] = size | 0xFF;
        size >>= 8;
        packBuf[1] = size | 0xFF;
        size >>= 8;
        packBuf[0] = size | 0xFF;
        return size + 4;
    }
    throw "PackBuffer overflow";
}

void Session::OnDataReceived(const boost::system::error_code &error, std::size_t bytes_transferred)
{
    if (error)
    {
        throw error;
    }
    if (isReadFrame)
    {
        isReadFrame = false;
        Message::Frame frame;
        if (frame.ParseFromArray(buf.begin(), bytes_transferred))
        {
            OnReceiveFrame(frame);
        }
        return;
    }
    size_t len = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
    assert(len < buf.size()); //"overflow buffer"
    isReadFrame = true;
    socket.async_receive(buffer(buf, len), std::bind(&Session::OnDataReceived, shared_from_this(),
                                                     std::placeholders::_1, std::placeholders::_2));
}

void Session::StartAsyncSend()
{
    int size = PackBuffer(frames.front());
    frames.pop_front();
    socket.async_send(
        boost::asio::buffer(packBuf.begin(), size),
        std::bind(&Session::OnSendFinished, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}

void Session::OnSendFinished(const boost::system::error_code &error, std::size_t bytes_transferred)
{
    if (error)
    {
        throw error;
    }
    if (!frames.empty())
    {
        StartAsyncSend();
    }
    else
    {
        isSendFinished = true;
    }
}

void Session::OnReceiveFrame(const Message::Frame &frame)
{
    frame.timestamp();
    if (frame.data().Is<Message::LoginReq>())
    {
        Message::LoginReq req;
        if (frame.data().UnpackTo(&req))
        {
            int id = db->Login(req.name(), req.passward());
            Message::Response rep;
            rep.set_code(id < 0 ? Message::Response::ResponseCode::Response_ResponseCode_Success
                                : Message::Response::ResponseCode::Response_ResponseCode_Fail);
            rep.mutable_values()->insert({0, PackInt(id)});
            SendFrame(rep);
        }
    }
    else if (frame.data().Is<Message::LogoutReq>())
    {
        Message::LogoutReq req;
        if (frame.data().UnpackTo(&req))
        {
            bool result = db->Logout(req.id());
            Message::Response rep;
            rep.set_code(result ? Message::Response::ResponseCode::Response_ResponseCode_Success
                                : Message::Response::ResponseCode::Response_ResponseCode_Fail);
            SendFrame(rep);
        }
    }
    else if (frame.data().Is<Message::GetInstancesReq>())
    {
        Message::GetInstancesReq req;
        if (frame.data().UnpackTo(&req))
        {
            auto result = db->GetInstanceList(req.filter());
            Message::Response rep;
            rep.set_code(!result.empty() ? Message::Response::ResponseCode::Response_ResponseCode_Success
                                         : Message::Response::ResponseCode::Response_ResponseCode_Fail);
            for (auto vp : result)
            {
                rep.mutable_values()->insert({vp.first, PackString(vp.second)});
            }
            SendFrame(rep);
        }
    }
    else if (frame.data().Is<Message::GetValueReq>())
    {
        Message::GetValueReq req;
        if (frame.data().UnpackTo(&req))
        {
            std::vector<int> ids;
            std::vector<int> keys;
            for (int i = 0; i < req.id_size(); i++)
            {
                ids.push_back(req.id(i));
            }
            for (int i = 0; i < req.key_size(); i++)
            {
                keys.push_back(req.key(i));
            }
            auto result = db->GetValues(ids, keys);
            if (!result.empty())
            {
                Message::Response rep;
                rep.set_code(Message::Response::ResponseCode::Response_ResponseCode_Success);
                for (auto vp : result)
                {
                    rep.clear_values();
                    for (auto valuepair : vp.second)
                    {
                        switch (valuepair.second.index())
                        {
                        case 0:
                            rep.mutable_values()->insert({valuepair.first, PackInt(std::get<int>(valuepair.second))});
                            break;
                        case 1:
                            rep.mutable_values()->insert(
                                {valuepair.first, PackFloat(std::get<float>(valuepair.second))});
                            break;
                        case 2:
                            rep.mutable_values()->insert(
                                {valuepair.first, PackDouble(std::get<double>(valuepair.second))});
                            break;
                        case 3:
                            rep.mutable_values()->insert(
                                {valuepair.first, PackString(std::get<std::string>(valuepair.second))});
                            break;
                        }
                    }
                    SendFrame(rep);
                }
            }
        }
    }
    else if (frame.data().Is<Message::SetValueReq>())
    {
        Message::SetValueReq req;
        if (frame.data().UnpackTo(&req))
        {
            std::map<int, DataBase::BasicValue> values;
            for (auto it = req.values().cbegin(); it != req.values().cend(); ++it)
            {
                DataBase::BasicValue value;
                switch (it->second.RealValue_case())
                {
                case Message::BasicValue::kInt32:
                    value.emplace<int>(it->second.int32());
                    break;
                case Message::BasicValue::kFloat:
                    value.emplace<float>(it->second.float_());
                    break;
                case Message::BasicValue::kDouble:
                    value.emplace<double>(it->second.double_());
                    break;
                case Message::BasicValue::kString:
                    value.emplace<std::string>(it->second.string());
                    break;
                default:
                    throw "undefien Message";
                }
                values[it->first] = value;
            }
            auto result = db->SetValue(req.id(), values);
            Message::Response rep;
            rep.set_code(result ? Message::Response::ResponseCode::Response_ResponseCode_Success
                                : Message::Response::ResponseCode::Response_ResponseCode_Fail);
            SendFrame(rep);
        }
    }
    else if (frame.data().Is<Message::Response>())
    {
        Message::Response rep;
        if (frame.data().UnpackTo(&rep))
        {
            rep.code();
            std::map<int, std::any> values;
            for (auto it = rep.values().cbegin(); it != rep.values().cend(); ++it)
            {
                values[it->first] = it->second;
            }
        }
        throw "Should not receive response in server";
    }
    else
    {
        throw "Unknow request or response";
    }
}
} // namespace MRMDS::Server